<template>
  <el-form label-width="100px">
    <el-form-item label="SPU名称">
      <el-input placeholder="请你输入SPU名称" v-model="SpuParams.spuName"></el-input>
    </el-form-item>
    <el-form-item label="SPU品牌">
      <el-select v-model="SpuParams.tmId">
        <el-option v-for="item in allTradeMark" :key="item.id" :label="item.tmName" :value="item.id"></el-option>
      </el-select>
    </el-form-item>
    <el-form-item label="SPU描述">
      <el-input type="textarea" placeholder="请你输入SPU描述" v-model="SpuParams.description"></el-input>
    </el-form-item>
    <el-form-item label="SPU图片">
      <!-- 照片墙 => fileList:展示默认图片, action:上传图片的接口地址, list-type:文件列表的类型(picture-card:照片墙), imgList:展示和收集的照片数组 -->
      <!-- 照片墙 => on-preview:点击预览按钮(放大镜)的时候触发的钩子了，on-remove： 点击删除按钮(垃圾桶)的时候触发的钩子， before-upload：上传文件之前的钩子，参数为上传的文件-->
      <el-upload
        v-model:file-list="imgList"
        action="/api/admin/product/fileUpload"
        list-type="picture-card"
        :on-preview="handlePictureCardPreview"
        :on-remove="handleRemove"
        :before-upload="handlerUpload"
      >
        <el-icon>
          <Plus />
        </el-icon>
      </el-upload>
      <!--点击预览按钮时,显示的图片放大的对话框-->
      <el-dialog v-model="dialogVisible">
        <!-- src: 存储显示的图片的地址。设置 w-full 类，使图片宽度为 100% -->
        <img class="w-full" :src="dialogImageUrl" alt="Preview Image" style="width: 100%; height: 100%" />
      </el-dialog>
    </el-form-item>
    <el-form-item label="SPU销售属性">
      <!-- 展示销售属性的下拉菜单 -->
      <el-select v-model="saleAttrIdAndValueName" :placeholder="unSelectSaleAttr.length ? `还未选择${unSelectSaleAttr.length}个` : '无'">
        <el-option :value="`${item.id}:${item.name}`" v-for="item in unSelectSaleAttr" :key="item.id" :label="item.name"></el-option>
      </el-select>
      <!-- 添加销售属性按钮 不选择下拉菜单则禁用-->
      <el-button @click="addSaleAttr" :disabled="!saleAttrIdAndValueName" style="margin-left: 10px" type="primary" size="default" icon="Plus">添加销售属性</el-button>
      <!-- 展示销售属性与属性值 -->
      <el-table border style="margin: 10px 0" :data="saleAttr">
        <el-table-column label="序号" type="index" align="center" width="80px"></el-table-column>
        <el-table-column label="销售属性名字" width="120px" prop="saleAttrName"></el-table-column>
        <el-table-column label="销售属性值">
          <!-- row:当前SPU已有的销售属性对象， 遍历的是这个销售属性对象的属性值数组 -->
          <template v-slot="{ row }">
            <!--closable属性使标签可以被关闭，当用户点击标签上的关闭按钮时，会触发 @close 事件(移除相应的属性值)。-->
            <el-tag style="margin: 0 5px" @close="row.spuSaleAttrValueList.splice(index, 1)" v-for="(item, index) in row.spuSaleAttrValueList" :key="item.id" class="mx-1" closable>
              {{ item.saleAttrValueName }}
            </el-tag>
            <!-- 编辑|显示属性值: 在input(row.flag == true)和button(row.flag == false)之间切换 -->
            <el-input @blur="toLook(row)" v-model="row.saleAttrValue" v-if="row.flag == true" placeholder="请你输入属性值" size="small" style="width: 100px"></el-input>
            <el-button @click="toEdit(row)" v-else type="primary" size="small" icon="Plus"></el-button>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="120px">
          <template v-slot="{ $index }">
            <!--删除属性对象按钮-->
            <el-button type="primary" size="small" icon="Delete" @click="saleAttr.splice($index, 1)"></el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-form-item>
    <el-form-item>
      <!-- 保存按钮: 禁用条件是销售属性一个都没有 -->
      <el-button :disabled="saleAttr.length <= 0" type="primary" size="default" @click="save">保存</el-button>
      <el-button type="primary" size="default" @click="cancel">取消</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
import type { AllTradeMark, HasSaleAttr, HasSaleAttrResponseData, SaleAttr, SaleAttrResponseData, SaleAttrValue, SpuData, SpuHasImg, Trademark } from '@/api/product/spu/type'
import { computed, ref } from 'vue'
import { reqAddOrUpdateSpu, reqAllSaleAttr, reqAllTradeMark, reqSpuHasSaleAttr, reqSpuImageList } from '@/api/product/spu'
import { ElMessage } from 'element-plus'

//定义自定义事件
let $emit = defineEmits(['changeScene'])
//点击取消按钮:通知父组件切换场景为0,展示已有SPU的数据(update->页码还停留在当前页)
const cancel = () => {
  $emit('changeScene', { flag: 0, params: 'update' })
}
//所有品牌
let allTradeMark = ref<Trademark[]>([])
//所有商品图片
// let imgList = ref<SpuImg[]>([]) // 打包不通过
let imgList = ref<any>([])
//已有SPU的销售属性
let saleAttr = ref<SaleAttr[]>([])
//全部销售属性
let allSaleAttr = ref<HasSaleAttr[]>([])
//控制图片放大对话框的显示与隐藏
let dialogVisible = ref<boolean>(false)
//存储预览图片的地址
let dialogImageUrl = ref<string>('')
//存储收集到的已有SPU对象 [新增]
let SpuParams = ref<SpuData>({
  category3Id: '', //三级分类的ID
  spuName: '', //SPU的名字
  description: '', //SPU的描述
  tmId: '', //品牌的ID
  spuImageList: [],
  spuSaleAttrList: [],
})
//将来收集还未选择的销售属性的ID(1,2,3)与属性值的名字(颜色,版本,尺码)
let saleAttrIdAndValueName = ref<string>('')
//子组件中发4个请求以获取完整已有SPU数据的方法 -- 修改SPU数据时使用
const initHasSpuData = async (spu: SpuData) => {
  //存储已有的SPU对象,将来在模板中展示。spu:父组件传递过来的已有SPU对象[不完整]
  SpuParams.value = spu // 收集已有SPU对象(不完整： 差spuImageList和spuSaleAttrList)到SpuParams中(包括新增的id、spuPosterList、createTime、undateTime)
  //获取全部品牌的数据
  let result1: AllTradeMark = await reqAllTradeMark()
  //获取某一个spu下的全部售卖商品的图片
  let result2: SpuHasImg = await reqSpuImageList(spu.id as number)
  //获取某一个spu下的已有SPU销售属性的数据
  let result3: SaleAttrResponseData = await reqSpuHasSaleAttr(spu.id as number)
  //获取全部SPU的销售属性
  let result4: HasSaleAttrResponseData = await reqAllSaleAttr()
  //全部品牌的数据
  allTradeMark.value = result1.data
  //SPU对应商品图片数组 -- 照片墙要求回显的图片对象必须包括name和url属性
  imgList.value = result2.data.map((item) => {
    return {
      name: item.imgName,
      url: item.imgUrl,
    }
  })
  //存储已有SPU的销售属性
  saleAttr.value = result3.data
  //存储全部的销售属性
  allSaleAttr.value = result4.data
}
//照片墙点击预览按钮的时候触发的钩子 -- 自动注入的参数file:当前点击的图片对象， 然后把图片的地址赋值给dialogImageUrl， 然后在图片放大对话框中显示出来
const handlePictureCardPreview = (file: any) => {
  dialogImageUrl.value = file.url
  //图片放大对话框弹出来
  dialogVisible.value = true
}
//照片墙点击删除按钮的时候触发的钩子 -- 这里不需要处理imgList照片数组，因为收集（包括添加和删除）照片的逻辑是在v-model中自动实现的
const handleRemove = () => {
  // console.log(123)
}
//照片墙上传文件之前的钩子， 约束文件的大小与类型 -- 自动注入的参数file:当前准备上传的图片对象 -- 这里不需要处理imgList照片数组，因为收集（包括添加和删除）照片的逻辑是在v-model中自动实现的
const handlerUpload = (file: any) => {
  if (file.type == 'image/png' || file.type == 'image/jpeg' || file.type == 'image/gif') {
    if (file.size / 1024 / 1024 < 3) {
      return true
    } else {
      ElMessage({
        type: 'error',
        message: '上传文件务必小于3M',
      })
      return false
    }
  } else {
    ElMessage({
      type: 'error',
      message: '上传文件务必PNG|JPG|GIF',
    })
    return false
  }
}

//计算出当前SPU还未拥有的销售属性 -- 这个计算属性的目的是找出在 allSaleAttr 中存在但在 saleAttr 中不存在的销售属性。
let unSelectSaleAttr = computed(() => {
  //举例 全部销售属性:颜色、版本、尺码，已有的销售属性:颜色、版本
  /* 整个计算属性是通过filter过滤allSaleAttr每一个item1返回一个数组（回调的返回值为true则过滤出来返回）， 再通过every（返回true|false）拿出saleAttr的每一个item2与item1的对应name比较，
     如果saleAttr中存在item2的saleAttrName与item1的name相同则返回false， 只有当saleAttr中不存在item2的saleAttrName与item1的name相同才返回true， 这样就过滤出来了allSaleAttr中存在但在saleAttr中不存在的销售属性
     第一个return是filter执行的回调返回值（数组）， 第二个和第三个return是every执行的回调返回值（布尔值）
   */
  return allSaleAttr.value.filter((item1) => {
    return saleAttr.value.every((item2) => {
      return item1.name != item2.saleAttrName
    })
  })
})

//添加销售属性的方法
const addSaleAttr = () => {
  const [baseSaleAttrId, saleAttrName] = saleAttrIdAndValueName.value.split(':') // 数组的解构赋值
  //准备一个新的销售属性对象,将来带给服务器即可
  let newSaleAttr: SaleAttr = {
    baseSaleAttrId,
    saleAttrName,
    spuSaleAttrValueList: [],
  }
  //追加到已有SPU的销售属性数组中
  saleAttr.value.push(newSaleAttr)
  //清空下拉框收集的数据
  saleAttrIdAndValueName.value = ''
}

//添加属性值按钮的点击事件
const toEdit = (row: SaleAttr) => {
  //input组件显示->编辑模式->清空已收集的属性值
  row.flag = true
  row.saleAttrValue = ''
}
//收集属性值表单元素input失去焦点的事件回调
const toLook = (row: SaleAttr) => {
  //解构出来收集到属性的ID与属性值
  const { baseSaleAttrId, saleAttrValue } = row
  //整理成服务器需要的属性值形式
  let newSaleAttrValue: SaleAttrValue = {
    baseSaleAttrId,
    saleAttrValueName: saleAttrValue as string, // as string: 由于 saleAttrValue 可能为 undefined(SaleAttr的ts类型的saleAttrValue字段有?)，所以需要进行类型断言
  }
  //判断属性值不能为空
  if ((saleAttrValue as string).trim() == '') {
    ElMessage({
      type: 'error',
      message: '属性值不能为空',
    })
    return
  }
  //判断属性值不能重复 -- find()方法返回的是数组中第一个满足条件的元素，如果没有找到则返回undefined
  let repeat = row.spuSaleAttrValueList.find((item) => {
    return item.saleAttrValueName == saleAttrValue
  })
  if (repeat) {
    ElMessage({
      type: 'error',
      message: '属性值重复',
    })
    return
  }
  //属性值数组中追加新的属性值对象
  row.spuSaleAttrValueList.push(newSaleAttrValue)
  //切换为查看模式
  row.flag = false
}

//保存按钮的回调
const save = async () => {
  //整理参数->发请求:添加SPU|更新已有的SPU->成功|失败
  //1:照片墙的数据
  SpuParams.value.spuImageList = imgList.value.map((item: any) => {
    return {
      imgName: item.name, //图片的名字
      /*
        它首先检查 item.response 是否存在，并且 item.response.data 是否存在。如果这两个条件都满足，那么 imgUrl 将被设置为 item.response.data。这是新增图片的情况。
        如果 item.response 或 item.response.data 不存在，那么 imgUrl 将被设置为 item.url。这是已有图片的情况。
      */
      imgUrl: (item.response && item.response.data) || item.url, //图片的地址
    }
  })
  //2:整理销售属性的数据
  SpuParams.value.spuSaleAttrList = saleAttr.value
  //3:发请求
  let result = await reqAddOrUpdateSpu(SpuParams.value)
  //4:提示信息
  if (result.code == 200) {
    ElMessage({
      type: 'success',
      message: SpuParams.value.id ? '更新成功' : '添加成功',
    })
    //通知父组件切换场景为0,并告诉父组件更新还是添加
    $emit('changeScene', {
      flag: 0,
      params: SpuParams.value.id ? 'update' : 'add',
    })
  } else {
    ElMessage({
      type: 'error',
      message: SpuParams.value.id ? '更新失败' : '添加失败',
    })
  }
}

//子组件中发2个请求以获取完整新增SPU数据的方法 -- 新增SPU数据时使用
const initAddSpu = async (c3Id: number | string) => {
  //清空数据
  Object.assign(SpuParams.value, {
    category3Id: '', //收集三级分类的ID
    spuName: '', //SPU的名字
    description: '', //SPU的描述
    tmId: '', //品牌的ID
    spuImageList: [],
    spuSaleAttrList: [],
  })
  //清空照片墙
  imgList.value = []
  //清空销售属性
  saleAttr.value = []
  //清空下拉框收集的数据
  saleAttrIdAndValueName.value = ''
  //存储三级分类的ID
  SpuParams.value.category3Id = c3Id
  //获取全部品牌的数据
  let result1: AllTradeMark = await reqAllTradeMark()
  //获取全部销售属性的数据
  let result2: HasSaleAttrResponseData = await reqAllSaleAttr()
  //存储数据
  allTradeMark.value = result1.data
  allSaleAttr.value = result2.data
}
//对外暴露 -- 这样父组件才能调用
defineExpose({ initHasSpuData, initAddSpu })
</script>

<style scoped></style>
